/**
 * DAQ functions.
 *
 * Matthew W. Bone;
 */
#include <pthread.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <string.h>
#include <comedilib.h>
#include <sys/mman.h>
#include <earthworm.h>
#include <transport.h>
#include <trace_buf.h>
#include "adsend_comedi.h"
#include "daq_functions.h"

/**
 * Here we take in a config file struct and use this information to initialize the 
 * Data Acquisition (DAQ) card.  This procedure initializes memory on the heap,
 * mainily a daq_handle structure.  It is the caller's reponsibility to free this
 * memory to avoid leaks (though this will not usually be necessary as the daq_handle
 * struct would probably always be used for the duration of the program).
 */
daq_handle *initialize_daq(ew_adsend_config_file *configfile) {
    
    daq_handle *handle = malloc(sizeof(daq_handle));

    handle->configfile = configfile;
    
    //attempt to open the device
    printf("going to open device: %s\n",configfile->Device);
    comedi_t *dev = comedi_open(configfile->Device);
    printf("dev %p\n", dev);
    if(!dev) {
	printf("Could not open device!");
	exit(-1);
    }

    handle->dev = dev;
    handle->subdevice = comedi_find_subdevice_by_type(dev, 
						      COMEDI_SUBD_AI, 0);

    handle->streaming_buffer_size = comedi_get_buffer_size(dev, 
							   handle->subdevice);

    //map the device to memory for faster reads:
    handle->map = mmap(NULL, handle->streaming_buffer_size, 
		       PROT_READ, MAP_SHARED, comedi_fileno(dev),0);

    if(*((int *)handle->map) == -1) {
	printf("not mapped correctly\n");
	exit(-1);
    }
  
    //allocate and initialize the channel list
    handle->channelList = malloc((configfile->ChannelListLength)
				 *sizeof(int));

    int i;
    for(i=0;i<configfile->ChannelListLength;i++) {
	handle->channelList[i]= CR_PACK(configfile->ChannelList[i].channel,
					configfile->ChannelList[i].range, 
					configfile->ChannelList[i].aref);
    }

    //allocate and initialize the COMEDI command

    handle->command = malloc(sizeof(comedi_command));
    memset(handle->command, 0, sizeof(comedi_command));
    comedi_get_cmd_generic_timed(dev, handle->subdevice, handle->command, configfile->PeriodNs);
    handle->command->scan_end_arg = configfile->ChannelListLength;
    
    handle->command->stop_src = TRIG_NONE; //Continuous acquisition
    handle->command->stop_arg = 0;
    handle->command->chanlist = handle->channelList;
    handle->command->chanlist_len = configfile->ChannelListLength;

    int isValid = comedi_command_test(dev, handle->command);
    if(isValid<0) {
	printf("Command not valid.\n");
	exit(-1);
    }

    //initialize other random fields:
    handle->front = 0;
    handle->back = 0;
    handle->current_chan = 0;
    handle->stop_flag = 0;

    return handle;
}

void start_acquisition(daq_handle *handle) {
  int ret = comedi_command(handle->dev, handle->command);
  if(ret<0) {
    printf("Could not execute command...exiting...\n");
    exit(-1);
  }
}

void stop_acquisition(daq_handle *handle) {
    comedi_cancel(handle->dev, handle->subdevice);
}

void get_results(daq_handle *handle, TraceDataQueue *queue) {    
    int i;
    int num_channels = handle->configfile->ChannelListLength;
    long samples_per_second = handle->configfile->SamplesPerSecond;
	
    struct timeval *endtime = malloc(sizeof(struct timeval));
    struct timeval *starttime;

    int ret = comedi_get_buffer_contents(handle->dev, handle->subdevice);
    if(ret>0) {
	//make sure that its not returning a negative 1 wich is an indication
	// of an error
	handle->back+=ret;
    } 

    while(handle->front==handle->back) {
	usleep(100);
	int bytes = comedi_get_buffer_contents(handle->dev, handle->subdevice); 
	if(bytes<0) {
	    //this means there's been some kind of an error
	    continue;
	} else {
	    handle->back+=bytes;
	}
    }
  
    gettimeofday(endtime, NULL);
    int total_num_samples = (handle->back-handle->front)/(sizeof(sampl_t));
    int total_num_samples_per_channel = total_num_samples/num_channels;

    starttime = calculate_starttime(endtime, total_num_samples_per_channel, 
				    samples_per_second);

    int temp_front = handle->front;
    free(endtime);
    
    while(temp_front<handle->back) {

	int temp_back = temp_front + (ADSEND_COMEDI_MAX_SAMPLES*num_channels*sizeof(sampl_t));
	if(temp_back>handle->back) {
	    temp_back = handle->back;
	}

	int num_samples = (temp_back-temp_front)/sizeof(sampl_t);
	int num_samples_per_channel = num_samples/num_channels;

	endtime = calculate_endtime(starttime, num_samples_per_channel, samples_per_second);
	TRACE_HEADER *header_array[ADSEND_COMEDI_MAX_CHANNELS];
	unsigned char *actual_memory_array[ADSEND_COMEDI_MAX_CHANNELS];
	long *values_array[ADSEND_COMEDI_MAX_CHANNELS];

	//allocate the space for the header_array & set the fields that we know:
	for(i=0;i<num_channels;i++) {
	    actual_memory_array[i] = get_next_blank_trace_header(queue);
	    header_array[i] = (TRACE_HEADER *) &actual_memory_array[i][0];
	    values_array[i] = (long *) &actual_memory_array[i][sizeof(TRACE_HEADER)];
	    header_array[i]->starttime = timeval_to_double(starttime);
	    header_array[i]->endtime = timeval_to_double(endtime);
		
	    //initialize the trace headers
	    header_array[i]->nsamp = (int) num_samples_per_channel;
	    
	    header_array[i]->samprate = handle->configfile->SamplesPerSecond;
		
	    header_array[i]->quality[0] = '\0';
	    header_array[i]->quality[1] = '\0';
		
	    //setup up chanlist appriately:
	    //this flag specifies what we are putting in the buffer...i.e. a short or an int.
	    strcpy(header_array[i]->datatype, "i4" );
	    strcpy(header_array[i]->sta,  handle->configfile->ChannelList[i].scn->sta );//Site name
	    strcpy(header_array[i]->net,  handle->configfile->ChannelList[i].scn->net); //Network name
	    strcpy(header_array[i]->chan, handle->configfile->ChannelList[i].scn->comp);//Component/channel code
	    header_array[i]->pinno = handle->configfile->ChannelList[i].scn->pin;
	}

	/* Once we get the data here, we know what data we have in the handle->map
	 * array.  Unfortunately the data in this array is multiplexed, meaning that
	 * this first valid index of map is from channel 1 at timestep t, the second is 
	 * from channel 2 also at timestep t, and so on.
	 * 	
	 * So we demultiplex the data and create an array of TRACE_HEADER  pointers
	 * to enqueue for the earthworm_packager thread.
	 */
	int list_indices[ADSEND_COMEDI_MAX_CHANNELS];
	for(i=0;i<num_channels;i++) {
	    list_indices[i] = 0;
	}
  	
	for(i=temp_front;i<temp_back;i+=sizeof(sampl_t)){
	    sampl_t read = *(sampl_t *)(handle->map+(i%handle->streaming_buffer_size));
	    
	    values_array[handle->current_chan][list_indices[handle->current_chan]] = (long) read;
    
	    list_indices[handle->current_chan]++;
	    handle->current_chan++;
	    if(handle->current_chan==num_channels) {
		handle->current_chan=0;
	    }
	}
  		
	for(i=0;i<num_channels;i++) {
	    next_trace_header_ready(queue);
	}
	
	temp_front = temp_back;
	free(starttime);
	starttime = endtime;
    }
    free(starttime);

    comedi_mark_buffer_read(handle->dev, handle->subdevice, handle->back-handle->front);
    handle->back = handle->back % handle->streaming_buffer_size;
    handle->front = handle->back;

}

struct timeval *calculate_starttime(struct timeval *endtime, int num_samples, long samples_per_second) {
    struct timeval *return_time = malloc(sizeof(struct timeval));
    double time = ((double)num_samples)/((double)samples_per_second);
    long sec = (long) time;
    long usec = (long) ((time-sec)*1000000);
    
    return_time->tv_sec = endtime->tv_sec - sec;
    return_time->tv_usec = endtime->tv_usec-usec;

    if(return_time->tv_usec < 0) {
	return_time->tv_usec += 1000000;
	return_time->tv_sec--;
    }

    return return_time;
}

struct timeval *calculate_endtime(struct timeval *starttime, int num_samples, long samples_per_second) {
    struct timeval *return_time = malloc(sizeof(struct timeval));
    double time = ((double)num_samples)/((double)samples_per_second);
    long sec = (long) time;
    long usec = (long) ((time-sec)*1000000);
    
    return_time->tv_sec = starttime->tv_sec + sec;
    return_time->tv_usec = starttime->tv_usec + usec;

    if(return_time->tv_usec < 0) {
	return_time->tv_usec += 1000000;
	return_time->tv_sec--;
    }

    return return_time;
}

double timeval_to_double(struct timeval *time) {
    double returnval;
    returnval = time->tv_sec;
    returnval += ((double)time->tv_usec)/(1000000.0);
    return returnval;
}
